#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
//#include <linux/spinlock.h>
#include "list.h"

static pthread_mutex_t syscall_queue_cs; /* a mutex */
static pthread_cond_t syscall_queue_cv; /* a condition variable */

FILE *neighbors_fp;
FILE *syscalls_fp;
FILE *packets_fp;
FILE *correlated_groups_fp;
FILE *correlated_syscalls_fp;
FILE *correlated_packets_fp;

#define TX 0
#define RX 1
#define NA -1

#define SEC_TIME_WINDOW 0
#define USEC_TIME_WINDOW 500000

struct neighbor {
	char src_vm[256];
	char destip[18];
	unsigned long destport;

	struct list_head list;
};

struct packet {
	//long fd;
	
	//unsigned long ticket;
	struct timeval tv;
	//unsigned long cr3;
	//unsigned long esp; /* or u32 */
	//unsigned long syscall_number;
	//char mode[4];
	int mode;
	char srcip[18];
	char destip[18];
	unsigned long srcport;
	unsigned long destport;
	
	struct list_head list;
};

struct syscall {
	char vm_ts[256];
	struct timeval tv;
	char syscall_type[16];
	unsigned long ticket;
	char tag[256];
	
	//char mode[4];
	int mode;
	char srcip[18];
	char destip[18];
	unsigned long srcport;
	unsigned long destport;
	
	struct list_head list;
};

struct packet *packet_new;
static LIST_HEAD (packet_list); /* List Head */
static LIST_HEAD (packet_group_list); /* List Head */

struct syscall *syscall_new;
static LIST_HEAD (syscall_list); /* List Head */
static LIST_HEAD (syscall_group_list); /* List Head */

struct neighbor *neighbor_new;
static LIST_HEAD (neighbor_list); /* List Head */



#define MAX_SYSCALL_QUEUE (100) /* Maximum length of queue */
#define MAX_PACKET_QUEUE (1000) /* Maximum length of queue */

static struct syscall *syscall_queue[MAX_SYSCALL_QUEUE];
//static int poolsize = 10;
static int syscall_queue_head = 0, syscall_queue_tail = 0;

int add_into_syscall_queue(struct syscall *syscall_new)
{
	int status = 0;
	int syscall_queue_next;

	if (pthread_mutex_lock(&syscall_queue_cs)) /* acquire lock queue_cs */
		fprintf(stderr, "error in mutex lock\n");

	syscall_queue_next = (syscall_queue_tail + 1) % MAX_SYSCALL_QUEUE;
	if (syscall_queue_head == syscall_queue_next) /* queue is full */
	{
		fprintf(stderr, "syscall_queue full\n");
		/* don't block on full queue, return SOAP_EOM */
		status = 1;
	}
	else
	{
		syscall_queue[syscall_queue_tail] = syscall_new; /* add a syscall_new */
		syscall_queue_tail = syscall_queue_next; /* update tail */

		fprintf(stderr, "add syscall_new\n");

		if (pthread_cond_signal(&syscall_queue_cv)) /*signal condition variable queue_cv*/
			fprintf(stderr, "error in signaling condition variable\n");
	}

	if (pthread_mutex_unlock(&syscall_queue_cs)) /*release lock queue_cs*/
		fprintf(stderr, "error in unlocking mutex\n");

	return status;
}

struct syscall* remove_from_syscall_queue() /* remove a syscall */
{
	struct syscall *syscall;
	if (pthread_mutex_lock(&syscall_queue_cs)) /*lock queue_cs*/
		fprintf(stderr, "error in locking mutex\n");

	while (syscall_queue_head == syscall_queue_tail) /* if the queue is empty */
		if (pthread_cond_wait(&syscall_queue_cv, &syscall_queue_cs)) /*block on variable queue_cv*/
			fprintf(stderr, "error in waiting condition variable\n");

	syscall = syscall_queue[syscall_queue_head];

	syscall_queue_head = (syscall_queue_head + 1) % MAX_SYSCALL_QUEUE;

	fprintf(stderr, "remove syscall\n");

	if (pthread_mutex_unlock(&syscall_queue_cs)) /*release lock queue_cs*/
		fprintf(stderr, "error in unlocking mutex\n");

	return syscall;
}

int send_neighbor(char *vm, char *ip, unsigned long port)
{
	if(port==0)
		return 1;
	//printf("send_neighbor:%s:%ld\n", ip, port);
	int flag = 0;
	struct neighbor *p;
	list_for_each_entry (p, &neighbor_list, list) {
		if(strcmp(p->src_vm, vm) == 0 && strcmp(p->destip, ip) == 0 && p->destport == port) {
			flag = 1;
			break;
		}
	}
	if(flag==0) //new neighbor
	{
		printf("new neighbor:%s:%ld\n", ip, port);
		neighbor_new = malloc (sizeof (struct neighbor));
		if(neighbor_new!=NULL) {
			strcpy(neighbor_new->src_vm, vm);
			strcpy(neighbor_new->destip, ip);
			neighbor_new->destport = port;
			list_add_tail (&neighbor_new->list, &neighbor_list);
			fprintf(neighbors_fp, "%s:%s:%ld\n", vm, ip, port);
			fflush(neighbors_fp);
		}
	}
	return 0;
}

int vhost_line_parse(char * line)
{
	char hostname[64];
	long tv_sec;
	long tv_usec;
	//char mode[4];

	char* token;
	char **split;
	int i, occurrence = 0;

	if(line==NULL)
		return -1;
	for(i=0; i<strlen(line); i++)
		if(line[i]==':')
			occurrence++;
	//printf("%s,occurrence:%d\n", line, occurrence);
	split = (char **)malloc(sizeof(char*)*(occurrence+1));
	for(i=0; i<(occurrence+1); i++)
	{
		split[i] = (char *)malloc(sizeof(char)*64);
	}

	token = strtok(line, ":");
	i = 0;
	while(token != NULL)
	{
		strcpy(split[i], token);
		//printf("%s\n", split[i]);
		i++;
		token = strtok(NULL, ":");
	}

	//strcpy(hostname, split[0]);
	//strcpy(mode, split[1]);

	packet_new = malloc (sizeof (struct packet));
	if(packet_new!=NULL) {
		//strcpy(packet_new->mode, split[1]);
		if(strcmp(split[1], "TX") == 0)
			packet_new->mode = TX;
		else if(strcmp(split[1], "RX") == 0)
			packet_new->mode = RX;
		else
			packet_new->mode = NA;
		strcpy(packet_new->srcip, split[2]);
		packet_new->srcport = atol(split[3]);
		strcpy(packet_new->destip, split[4]);
		packet_new->destport = atol(split[5]);
		packet_new->tv.tv_sec = atol(split[6]);
		packet_new->tv.tv_usec = atol(split[7]);
		list_add_tail (&packet_new->list, &packet_list);
	}

	for(i=0; i<(occurrence+1); i++)
	{
		free(split[i]);
	}
	return 0;
}

int rx_or_tx(char * syscall_name)
{
	if(strcmp(syscall_name, "SYS_SENDTO") == 0)
		return TX;//0 is tx
	if(strcmp(syscall_name, "SYS_RECVFROM") == 0)
		return RX;//1 is rx
	return NA;
}

int syscall_line_parse(char * line)
{
	char* token;
	char **split;
	int i, occurrence = 0;

	if(line==NULL)
		return -1;
	for(i=0; i<strlen(line); i++)
		if(line[i]==':')
			occurrence++;
	//printf("%s,occurrence:%d\n", line, occurrence);
	split = (char **)malloc(sizeof(char*)*(occurrence+1));
	for(i=0; i<(occurrence+1); i++)
	{
		split[i] = (char *)malloc(sizeof(char)*64);
	}

	token = strtok(line, ":");
	i = 0;
	while(token != NULL)
	{
		strcpy(split[i], token);
		//printf("%s\n", split[i]);
		i++;
		token = strtok(NULL, ":");
	}

	syscall_new = malloc (sizeof (struct syscall));
	//syscall_new = remove_from_syscall_queue();
	if(syscall_new!=NULL) {
		strcpy(syscall_new->vm_ts, split[0]);
		strcpy(syscall_new->tag, split[1]);
		//if(rx_or_tx(split[2])==0)
		//	strcpy(syscall_new->mode, "TX");
		//if(rx_or_tx(split[2])==1)
		//	strcpy(syscall_new->mode, "RX");
		strcpy(syscall_new->syscall_type, split[2]);
		syscall_new->mode = rx_or_tx(split[2]);
		syscall_new->ticket=atol(split[3]);
		strcpy(syscall_new->srcip, split[4]);
		syscall_new->srcport = atol(split[5]);
		strcpy(syscall_new->destip, split[6]);
		syscall_new->destport = atol(split[7]);
		syscall_new->tv.tv_sec = atol(split[8]);
		syscall_new->tv.tv_usec = atol(split[9]);
		send_neighbor(split[0], syscall_new->destip, syscall_new->destport);
		list_add_tail (&syscall_new->list, &syscall_list);
	}

	for(i=0; i<(occurrence+1); i++)
	{
		free(split[i]);
	}
	return 0;
}

void * vhost_proc_read(char * procname)
{
	FILE *fp;
	//char line[1024*1024];
	char line[512];
	while(1) {
		if( (fp = fopen(procname, "r" )) != NULL )
		{
			 //len = fread( line, 1, 1024*1024, fp );
				//	printf( "len:%d,%s", len, line);
			//while(!feof(fp)){
			while(1) {
				if( fgets( line, 512, fp ) != NULL) {
					//printf( "%s", line);
					fprintf(packets_fp, "%s", line);
					fflush(packets_fp);
					vhost_line_parse(line);
				}
				else {
					//printf( "fgets error\n" );
					break;
				}
			}
			//printf("Finish reading\n");
			fclose( fp );
		}
	//	sleep(1);
	}
	//pthread_exit(0);
}



int syscall_packet_correlation()
{
	//didn't add tag comparison
	int syscall_group_list_size = 0;
	int packet_group_list_size = 0;
	unsigned long ticket = 0;
	char vm_ts[256];
	char tag[256];
	struct timeval start_tv;
	struct timeval end_tv;
	char srcip[18];
	char destip[18];
	unsigned long srcport = 0;
	unsigned long destport = 0;

	struct list_head *pos,*n;
	struct syscall *syscall;
	struct packet *packet;

	strcpy(srcip, "0.0.0.0");
	strcpy(destip, "0.0.0.0");

	//first find a group of syscalls with the same ticket number
	list_for_each_entry(syscall, &syscall_list, list) {
		ticket = syscall->ticket;
		strcpy(vm_ts, syscall->vm_ts);
		strcpy(tag, syscall->tag);
		start_tv.tv_sec = syscall->tv.tv_sec;
		start_tv.tv_usec = syscall->tv.tv_usec;
		break;
	}
	list_for_each_safe(pos, n, &(syscall_list)) {
		syscall = list_entry(pos, struct syscall, list);
		if(syscall->ticket == ticket&&strcmp(syscall->vm_ts, vm_ts) == 0)
		{
			//the same group, since the same ticket and the same vm
			list_del(pos);
			list_add_tail (&syscall->list, &syscall_group_list);
			syscall_group_list_size ++;
			if(strcmp(syscall->srcip, "0.0.0.0")!=0)
			{
				strcpy(srcip, syscall->srcip);
				srcport = syscall->srcport;
			}
			if(strcmp(syscall->destip, "0.0.0.0")!=0)
			{
				strcpy(destip, syscall->destip);
				destport = syscall->destport;
			}
		}
	}
	//now syscall points to the last of syscall, just use its ts
	//end_tv.tv_sec = syscall->tv.tv_sec;
	end_tv.tv_sec = syscall->tv.tv_sec + SEC_TIME_WINDOW;
	end_tv.tv_usec = syscall->tv.tv_usec + USEC_TIME_WINDOW;

	if(syscall_group_list_size == 0) {
		printf("syscall_group_list_size is zero\n");
		return -1;
	}

	//search packets in vhost trace
	vhost_proc_read("/proc/vhost");
	list_for_each_safe(pos, n, &(packet_list)) {
		packet = list_entry(pos, struct packet, list);
		if(packet->tv.tv_sec<start_tv.tv_sec) { //old packet before the start of syscall group
			list_del(pos);
			free(packet);
			continue;
		}
		else if(packet->tv.tv_sec==start_tv.tv_sec&&packet->tv.tv_usec<=start_tv.tv_usec) { //old packet before the start of syscall group
			list_del(pos);
			free(packet);
			continue;
		}
		else { //consdier time window
			//packet is before end_tv
			if(packet->tv.tv_sec<end_tv.tv_sec||
					(packet->tv.tv_sec==end_tv.tv_sec&&packet->tv.tv_usec<=end_tv.tv_usec)) {
				if(packet->mode == TX) {//should look at destip of packet
					if(strcmp(packet->destip, destip)==0&&packet->destport==destport) {
						list_del(pos);
						list_add_tail (&packet->list, &packet_group_list);
						printf("find a TX match: ts: %ld.%ld, src:%s:%ld,dest:%s:%ld\n",
								packet->tv.tv_sec, packet->tv.tv_usec, packet->srcip, packet->srcport, packet->destip, packet->destport);
					}
				}
				else if(packet->mode == RX) {//should look at srcip of packet
					if(strcmp(packet->srcip, destip)==0&&packet->srcport==destport) {
						list_del(pos);
						list_add_tail (&packet->list, &packet_group_list);
					}
				}
				else {//impossible to go here, since packet mode only have two options: RX and TX
				}
				packet_group_list_size ++;
			}
		}
	}

	fprintf(correlated_groups_fp, "#vm %s group %ld starts\n", vm_ts, ticket);
	//fprintf(correlated_groups_fp, "syscalls:\n");
	//start to output and cleanup
	list_for_each_safe(pos, n, &(syscall_group_list)) {
		syscall = list_entry(pos, struct syscall, list);
		list_del(pos);
		//recycle syscall struct
		//add_into_syscall_queue(syscall);
		//fprintf(correlated_groups_fp, "%s,%s:%ld,%s:%ld,ticket:%ld,ts:%ld.%ld\n", syscall->syscall_type,
		//		syscall->srcip,syscall->srcport,
		//		syscall->destip,syscall->destport,syscall->ticket,syscall->tv.tv_sec,syscall->tv.tv_usec);
		fprintf(correlated_groups_fp, "%s:%s:%ld:%s:%s:%ld:%s:%ld:%ld:%ld\n", vm_ts,tag,ticket,
						syscall->syscall_type,syscall->srcip,syscall->srcport,syscall->destip,
						syscall->destport,syscall->tv.tv_sec,syscall->tv.tv_usec);
		fflush(correlated_groups_fp);

		fprintf(correlated_syscalls_fp, "%s:%s:%ld:%s:%s:%ld:%s:%ld:%ld:%ld\n", vm_ts,tag,ticket,
				syscall->syscall_type,syscall->srcip,syscall->srcport,syscall->destip,
				syscall->destport,syscall->tv.tv_sec,syscall->tv.tv_usec);
		fflush(correlated_syscalls_fp);

		free(syscall);
	}
	fprintf(correlated_syscalls_fp, "\n");
	fprintf(correlated_groups_fp, "packets:\n");
	list_for_each_safe(pos, n, &(packet_group_list)) {
		packet = list_entry(pos, struct packet, list);
		list_del(pos);
		//recycle syscall struct
		//add_into_syscall_queue(syscall);
		//fprintf(correlated_groups_fp, "src:%s:%ld,dest:%s:%ld, ts: %ld.%ld\n",
		//								packet->srcip, packet->srcport, packet->destip,
		//								packet->destport,packet->tv.tv_sec, packet->tv.tv_usec);
		fprintf(correlated_groups_fp, "%s:%s:%ld:%s:%ld:%s:%ld:%ld:%ld\n", vm_ts,tag,ticket,packet->srcip, packet->srcport, packet->destip,
						packet->destport,packet->tv.tv_sec, packet->tv.tv_usec);
		fflush(correlated_groups_fp);

		fprintf(correlated_packets_fp, "%s:%s:%ld:%s:%ld:%s:%ld:%ld:%ld\n", vm_ts,tag,ticket,packet->srcip, packet->srcport, packet->destip,
				packet->destport,packet->tv.tv_sec, packet->tv.tv_usec);
		fflush(correlated_packets_fp);

		free(packet);
	}
	fprintf(correlated_packets_fp, "\n");
	syscall_group_list_size = 0;
	packet_group_list_size = 0;
	fprintf(correlated_groups_fp, "#vm %s group %ld ends\n", vm_ts, ticket);
	fflush(correlated_groups_fp);
	return 0;
}
void * syscall_proc_read(char * procname)
{
	FILE *fp;
	//char line[1024*1024];
	char line[512];
	int diff = 0;
	while(1) {
		if( (fp = fopen(procname, "r" )) != NULL )
		{
			 //len = fread( line, 1, 1024*1024, fp );
				//	printf( "len:%d,%s", len, line);
			//while(!feof(fp)){
			while(1) {
				if( fgets( line, 512, fp ) != NULL) {
					//printf( "%s", line);
					fprintf(syscalls_fp, "%s", line);
					fflush(syscalls_fp);
					syscall_line_parse(line);
					/*diff = syscall_queue_tail - syscall_queue_head;
					if(diff>=20||(diff>=-80&&diff>0)) //enough syscalls for followup correlations
					{
						syscall_packet_correlation();
					}*/
				}
				else {
					//printf( "fgets error\n" );
					break;
				}
			}
			//printf("Finish reading\n");
			fclose( fp );
		}
		syscall_packet_correlation();
		sleep(1);
	}
	//pthread_exit(0);
}

int main(int argc, char* argv[])
{
	int i;
	pthread_t syscall_thread, vhost_thread;
	char syscall_proc[32];
	char vhost_proc[32];
	strcpy(syscall_proc, "/proc/kvm_trace");
	strcpy(vhost_proc, "/proc/vhost");
	neighbors_fp = fopen("neighbors.txt", "w");
	syscalls_fp = fopen("syscalls.txt", "w");
	packets_fp = fopen("packets.txt", "w");
	correlated_groups_fp = fopen("correlated_groups.txt", "w");
	correlated_packets_fp = fopen("correlated_packets.txt", "w");
	correlated_syscalls_fp = fopen("correlated_syscalls.txt", "w");
	//vhost_proc_read("/proc/vhost");
	//syscall_proc_read("/proc/kvm-crumbs");

	for(i=0;i<MAX_SYSCALL_QUEUE;i++)
	{
		syscall_queue[i] = malloc (sizeof (struct syscall));
	}

	pthread_mutex_init(&syscall_queue_cs, NULL); /* setup lock */
	pthread_cond_init(&syscall_queue_cv, NULL); /* setup condition variable */

	//syscall_proc_read(syscall_proc);
	vhost_proc_read(vhost_proc);
	//pthread_create(&syscall_thread,NULL,syscall_proc_read,syscall_proc);
	//pthread_create(&vhost_thread,NULL,vhost_proc_read,vhost_proc);
	//pthread_join(syscall_thread,NULL);
	//pthread_join(vhost_thread,NULL);
	pthread_cond_destroy(&syscall_queue_cv); /* cleanup condition variable */
	pthread_mutex_destroy(&syscall_queue_cs); /* cleanup lock */
	return 0;
}
